-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
x509 Verification: Extension policies documentation. #11800
x509 Verification: Extension policies documentation. #11800
Conversation
d7c5ebd
to
649dd50
Compare
docs/x509/verification.rst
Outdated
.. staticmethod:: webpki_defaults_ca() | ||
|
||
Creates an ExtensionPolicyBuilder initialized with a | ||
CA extension policy based on CA/B Forum guidelines. | ||
|
||
This is the CA extension policy used by :class:`PolicyBuilder`. | ||
|
||
:returns: An instance of :class:`ExtensionPolicyBuilder` | ||
|
||
.. staticmethod:: webpki_defaults_ee() | ||
|
||
Creates an ExtensionPolicyBuilder initialized with an | ||
EE extension policy based on CA/B Forum guidelines. | ||
|
||
This is the EE extension policy used by :class:`PolicyBuilder`. | ||
|
||
:returns: An instance of :class:`ExtensionPolicyBuilder` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@woodruffw right now our extension policies are basically "CABF, but with some things loosened where necessary in practice", is there a good way of articulating that for users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@woodruffw right now our extension policies are basically "CABF, but with some things loosened where necessary in practice", is there a good way of articulating that for users?
Sorry, missed this ping originally!
Maybe something like "these extension policies are intended to be strictly consistent with CABF, which dictates the requirements for certificates on the public web. However, we make limited exceptions to CABF where necessary in practice, i.e. behaviors that CABF forbids but are nonetheless present in the current Web PKI."
But maybe that's too much of a mouthful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can put it in a glossary entry or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure how to formulate this as a glossary entry. What would be the entry name? I would appreciate if someone could write out the full text of the entry here (or just made a separate PR for this).
Hey, @alex, friendly ping 😄 |
Not forgotten! On my TODO list, hopefully this evening. Thanks so much for your patience, and sorry I've been so slow. Don't feel bad about pinging me. |
No worries at all, my plate is also very full right now, so I wouldn't have gotten to doing any actual changes before this weekend at the earliest anyways. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few tiny notes, but overall I think this design works.
Not going to merge until we have an implementation of course.
Hi, sorry for disappearing for so long - other responsibilities caught up to me and I couldn't find the time unfortunately. I'll start preparing the main PR for primetime now, hopefully I'll be able to open it this year 😅 |
0639af6
to
bf4412d
Compare
*rebased on main to keep my local branches in sync and based on main. |
bf4412d
to
8039cb2
Compare
Another rebase on main. I'm going to finally create the main PR now. |
Great!
…On Wed, Jan 29, 2025 at 9:26 AM Ivan Desiatov ***@***.***> wrote:
Another rebase on main. I'm going to finally create the main PR now.
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBCB6N2XO2XAB6ISSUT2NDQKHAVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMMRRHAYDKMZZHA>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
--
All that is necessary for evil to succeed is for good people to do nothing.
|
Converting to draft since this will require edits after the implementation gets into main. |
8039cb2
to
627dc16
Compare
627dc16
to
1f6c27c
Compare
docs/x509/verification.rst
Outdated
@@ -181,6 +179,11 @@ the root of trust: | |||
|
|||
.. versionadded:: 42.0.0 | |||
|
|||
.. versionchanged:: 45.0.0 | |||
``subject``, ``verification_time`` and ``max_chain_depth`` were replaced by the | |||
``policy`` property that provides access to these values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an established way how deprecated properties are treated in the docs? I did a quick search and couldn't find anything like a deprecated flag in sphinx Should the properties be kept in the docs and somehow marked as deprecated or removed altogether?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found the deprecated
sphinx attribute, but it doesn't seem to be intended for use on a method level. For now I changed the text in the versionchanged
description to reflect that the properties were deprecated, but didn't add them back as separate attribute
s. I think that strikes a good balance between acknowledging the change and not cluttering the docs with deprecated functionality.
Not about the docs, but I just realized: Using custom extension policies + a server verifier will result in the SAN not actually being checked, right? I think we need to fix that. |
Yes, the current implementation allows to create a ServerVerifier that doesn't check SANs match if you explicitly replace the validator. The default validator for SubjectAlternativeName does two things - checks criticality: match (cert.certificate().subject().is_empty(), extn.critical) {
// If the subject is empty, the SAN MUST be critical.
(true, false) => {
return Err(ValidationError::new(ValidationErrorKind::Other(
"EE subjectAltName MUST be critical when subject is empty".to_string(),
)));
}
// If the subject is non-empty, the SAN MUST NOT be critical.
(false, true) => {
return Err(ValidationError::new(ValidationErrorKind::Other(
"EE subjectAltName MUST NOT be critical when subject is nonempty".to_string(),
)))
}
_ => (),
}; and compares // NOTE: We only verify the SAN against the policy's subject if the
// policy actually contains one. This enables both client and server
// profiles to use this validator, **with the expectation** that
// server profile construction requires a subject to be present.
if let Some(sub) = policy.subject.as_ref() {
let san: SubjectAlternativeName<'_> = extn.value()?;
if !sub.matches(&san) {
return Err(ValidationError::new(ValidationErrorKind::Other(
"leaf certificate has no matching subjectAltName".into(),
)));
}
} It does imo make sense to make the second check mandatory, but not the first one - if someone needs to handle a certificate with incorrect criticality there, I think we should let them. So what I suggest is to always explicitly run the name comparison (second check), irrespective of what the extension policy says. If the However we still have to prohibit the extension policy used with a server verifier from permitting certificates without the SAN extension. Unfortunately the "earliest" place where this can be done is in |
To summarize, what I suggest is always checking the name in a |
I think we can accomplish most (if not all?) of what we need by moving the
policy.subject check to be outside of the extension policy, and move it
into the core of the verifier. Does that make sense?
…On Thu, Feb 13, 2025 at 11:31 AM Ivan Desiatov ***@***.***> wrote:
Yes, the current implementation allows to create a ServerVerifier that
doesn't check SANs match if you explicitly replace the validator.
The default validator for SubjectAlternativeName does two things - checks
criticality:
match (cert.certificate().subject().is_empty(), extn.critical) {
// If the subject is empty, the SAN MUST be critical.
(true, false) => {
return Err(ValidationError::new(ValidationErrorKind::Other(
"EE subjectAltName MUST be critical when subject is empty".to_string(),
)));
}
// If the subject is non-empty, the SAN MUST NOT be critical.
(false, true) => {
return Err(ValidationError::new(ValidationErrorKind::Other(
"EE subjectAltName MUST NOT be critical when subject is nonempty".to_string(),
)))
}
_ => (),};
and compares subject to the value in the extension:
// NOTE: We only verify the SAN against the policy's subject if the// policy actually contains one. This enables both client and server// profiles to use this validator, **with the expectation** that// server profile construction requires a subject to be present.if let Some(sub) = policy.subject.as_ref() {
let san: SubjectAlternativeName<'_> = extn.value()?;
if !sub.matches(&san) {
return Err(ValidationError::new(ValidationErrorKind::Other(
"leaf certificate has no matching subjectAltName".into(),
)));
}}
It does imo make sense to make the second check mandatory, but not the
first one - if someone needs to handle a certificate with wrong criticality
there, I think we should let them.
So what I suggest is to always explicitly run the name comparison (second
check), irrespective of what the extension policy says. If the
ExtensionPolicy *does* have a SAN validator, we will run both that and
the name match check.
However we still have to prohibit the extension policy used with a server
verifier from permitting certificates without the SAN extension.
Unfortunately the "earliest" place where this can be done is in
build_server_verifier since before that we don't know what the
ExtensionPolicy will apply to. So I think this should be part of
validation performed in cryptography_x509_verification::Policy::new,
which will check that the ExtensionPolicy requires the SAN extension to
be present whenever Policy.subject is not None.
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBHJCW375AD2OBWGIE32PTCFTAVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJXGE2DGNZXG4>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
[image: deivse]*deivse* left a comment (pyca/cryptography#11800)
<#11800 (comment)>
Yes, the current implementation allows to create a ServerVerifier that
doesn't check SANs match if you explicitly replace the validator.
The default validator for SubjectAlternativeName does two things - checks
criticality:
match (cert.certificate().subject().is_empty(), extn.critical) {
// If the subject is empty, the SAN MUST be critical.
(true, false) => {
return Err(ValidationError::new(ValidationErrorKind::Other(
"EE subjectAltName MUST be critical when subject is empty".to_string(),
)));
}
// If the subject is non-empty, the SAN MUST NOT be critical.
(false, true) => {
return Err(ValidationError::new(ValidationErrorKind::Other(
"EE subjectAltName MUST NOT be critical when subject is nonempty".to_string(),
)))
}
_ => (),};
and compares subject to the value in the extension:
// NOTE: We only verify the SAN against the policy's subject if the// policy actually contains one. This enables both client and server// profiles to use this validator, **with the expectation** that// server profile construction requires a subject to be present.if let Some(sub) = policy.subject.as_ref() {
let san: SubjectAlternativeName<'_> = extn.value()?;
if !sub.matches(&san) {
return Err(ValidationError::new(ValidationErrorKind::Other(
"leaf certificate has no matching subjectAltName".into(),
)));
}}
It does imo make sense to make the second check mandatory, but not the
first one - if someone needs to handle a certificate with wrong criticality
there, I think we should let them.
So what I suggest is to always explicitly run the name comparison (second
check), irrespective of what the extension policy says. If the
ExtensionPolicy *does* have a SAN validator, we will run both that and
the name match check.
However we still have to prohibit the extension policy used with a server
verifier from permitting certificates without the SAN extension.
Unfortunately the "earliest" place where this can be done is in
build_server_verifier since before that we don't know what the
ExtensionPolicy will apply to. So I think this should be part of
validation performed in cryptography_x509_verification::Policy::new,
which will check that the ExtensionPolicy requires the SAN extension to
be present whenever Policy.subject is not None.
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBHJCW375AD2OBWGIE32PTCFTAVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJXGE2DGNZXG4>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
--
All that is necessary for evil to succeed is for good people to do nothing.
|
That sounds great. (I don't know if we _need_ the 2nd part, but it's
not a bad idea)
…On Thu, Feb 13, 2025 at 11:34 AM Ivan Desiatov ***@***.***> wrote:
To summarize, what I suggest is always checking the name in a ServerVerifier, irrespective of the ExtensionPolicy's validator, and build_server_verifier raising an exception if the ExtensionPolicy has anything except must_be_set for the SAN extension. If this sounds good, I can make a PR tomorrow.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
deivse left a comment (pyca/cryptography#11800)
To summarize, what I suggest is always checking the name in a ServerVerifier, irrespective of the ExtensionPolicy's validator, and build_server_verifier raising an exception if the ExtensionPolicy has anything except must_be_set for the SAN extension. If this sounds good, I can make a PR tomorrow.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
--
All that is necessary for evil to succeed is for good people to do nothing.
|
Yes, the second part isn't needed, I was just slightly prioritizing the user experience here - as a user I would prefer to get an exception the moment I provide an incorrect argument to the library (extension policy that doesn't require SAN passed to |
If the code isn't too complicated, I agree that it's a nice UX improvement.
…On Thu, Feb 13, 2025 at 11:38 AM Ivan Desiatov ***@***.***> wrote:
Yes, the second part isn't needed, I was just slightly prioritizing the
user experience here - as a user I would prefer to get an exception the
moment I provide an incorrect argument to the library (extension policy
that doesn't require SAN passed to build_server_verifier), instead of it
happening later when I call another function (ServerVerifier.verify). It's
up to you ultimately, I'm not sure if the UX improvement outweighs the
extra complexity here.
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBBNOWVATQLORODGDDT2PTC7VAVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJXGE3DEMBXGQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
[image: deivse]*deivse* left a comment (pyca/cryptography#11800)
<#11800 (comment)>
Yes, the second part isn't needed, I was just slightly prioritizing the
user experience here - as a user I would prefer to get an exception the
moment I provide an incorrect argument to the library (extension policy
that doesn't require SAN passed to build_server_verifier), instead of it
happening later when I call another function (ServerVerifier.verify). It's
up to you ultimately, I'm not sure if the UX improvement outweighs the
extra complexity here.
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBBNOWVATQLORODGDDT2PTC7VAVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJXGE3DEMBXGQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
--
All that is necessary for evil to succeed is for good people to do nothing.
|
Ok, I'll implement it tomorrow and see if it makes sense to include the check in |
Great! Thank you for all the work on this, it's been fantastic! I'll review
these docs later today.
…On Thu, Feb 13, 2025 at 11:42 AM Ivan Desiatov ***@***.***> wrote:
Ok, I'll implement it tomorrow and see if it makes sense to include the
check or not.
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBA737DC54YSANHDX2D2PTDOJAVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJXGE3TEMBZGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
[image: deivse]*deivse* left a comment (pyca/cryptography#11800)
<#11800 (comment)>
Ok, I'll implement it tomorrow and see if it makes sense to include the
check or not.
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBA737DC54YSANHDX2D2PTDOJAVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJXGE3TEMBZGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
--
All that is necessary for evil to succeed is for good people to do nothing.
|
:type: int | ||
|
||
.. type:: MaybeExtensionValidatorCallback | ||
:canonical: Callable[[Policy, Certificate, Optional[ExtensionType]], None] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is this the right way to document the generic parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also like to know 😄 I was trying to find documentation on how this is supposed to be done, but I didn't find anything specific to type aliases. I did try a couple things just now:
-
Version A
.. type:: MaybeExtensionValidatorCallback[T: ExtensionType] :canonical: Callable[[Policy, Certificate, Optional[T]], None]
results in
which is a bit too cluttered imo, and also triggers a warningverification.rst:445: WARNING: py:class reference target not found: T [ref.class]
-
Version B
.. type:: MaybeExtensionValidatorCallback :canonical: Callable[[Policy, Certificate, Optional[T:ExtensionType]], None]
This is less cluttered, but doesn't show that this is a generic type as well, and still triggers a warning.
I don't really know if there is a proper way to do this without triggering warnings. I couldn't find anything in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are also methods on ExtensionPolicy
which are generic, but I didn't document as such in this PR. After doing some quick research, I think the way this is intended to be used might be incompatible with how the cryptography docs are structured. The sphinx docs suggest here that generic functions/methods should be documented as such:
.. py:function:: add[T](a: T, b: T) -> T
but cryptography docs don't put types in the declaration there, just the names (from what I saw at least). And using a generic "defined" in the function declaration part of the doc in the argument documentation once again results in warnings and looks too cluttered imo anyway:
.. method:: may_be_present[T: ExtensionType](extension_type, criticality, validator_cb)
Specifies that the extension...
:param type[T] extension_type: A concrete class derived from :type:`~cryptography.x509.ExtensionType`
indicating which extension may be present.
:param Criticality criticality: The criticality of the extension
:param validator_cb: An optional Python callback to validate the extension value.
Must accept extensions of type `extension_type`.
:type validator_cb: :type:`MaybeExtensionValidatorCallback[T]` or None
Warnings:
verification.rst:2: WARNING: py:class reference target not found: T [ref.class]
verification.rst:367: WARNING: py:type reference target not found: MaybeExtensionValidatorCallback[T] [ref.type]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I think I might prefer to leave the docs as is even if there is a proper way to document this without the warnings - I think the API is understandable without this in the docs, and it should be freely and conveniently available for anyone actually using the library with a type checker, while adding this might actually make the docs somewhat harder to parse visually and make things seem more complex than they are from a user perspective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PS: Even if this is not the right way to document this, writing ExtensionType
does communicate that any T: ExtensionType
will be accepted. The only thing we lose is the information that the type alias is generic, which I'm not sure is that important to the user.
Just realized there are still a couple important things missing from the docs:
|
I think the only thing remaining is a CHANGELOG entry! |
Well I was just editing the docs so it mentions that an EE extension policy must require SAN to be present if it's used to build a ServerVerifier. Do you think that it's not worth mentioning as it's implied and the exception is self-explanatory, or should I make another PR? |
I don't feel strongly about it. If you've already got some suggested
language, it's probably worth including.
…On Fri, Feb 14, 2025 at 10:09 AM Ivan Desiatov ***@***.***> wrote:
Well I was just editing the docs so it mentions that an EE extension
policy must require SAN to be present if it's used to build a
ServerVerifier. Do you think that it's not worth mentioning as it's implied
and the exception is self-explanatory, or should I make another PR?
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBFFGSSEWKZEIQLJ2U32PYBJ3AVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJZGU3TKNZQGE>
.
You are receiving this because you modified the open/close state.Message
ID: ***@***.***>
[image: deivse]*deivse* left a comment (pyca/cryptography#11800)
<#11800 (comment)>
Well I was just editing the docs so it mentions that an EE extension
policy must require SAN to be present if it's used to build a
ServerVerifier. Do you think that it's not worth mentioning as it's implied
and the exception is self-explanatory, or should I make another PR?
—
Reply to this email directly, view it on GitHub
<#11800 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBFFGSSEWKZEIQLJ2U32PYBJ3AVCNFSM6AAAAABQHKN5M2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMNJZGU3TKNZQGE>
.
You are receiving this because you modified the open/close state.Message
ID: ***@***.***>
--
All that is necessary for evil to succeed is for good people to do nothing.
|
Finally coming back with another PR. The idea is that this one will serve to confirm the user-facing API for custom extension policy support, and the next one will have the first parts of the implementation.